package io.elegant.sessions

import android.content.Context
import com.google.protobuf.Any
import io.elegant.Elegant
import io.elegant.ElegantApiGrpc.ElegantApiStub
import io.elegant.toAny
import io.elegant.toStringValue
import io.grpc.stub.StreamObserver
import kotlinx.coroutines.launch
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okio.IOException
import org.json.JSONObject
import java.io.ByteArrayOutputStream
import okio.Buffer
import java.util.zip.GZIPOutputStream

const val ERR_CODE = 404

class RequestsSession(
    stub: ElegantApiStub,
    context: Context
) : BaseSession(stub, context) {
    override val sendStreamObserver: StreamObserver<Elegant.Interaction> = stub.requests(receiveStreamObserver)
    private var client: OkHttpClient? = null

    override fun onCreate(sessionId: Long, futureId: Long, vararg argv: Any) {
        super.onCreate(sessionId, futureId, *argv)
        client = OkHttpClient()
    }

    override suspend fun onReceive(name: String, sessionId: Long, futureId: Long, vararg argv: Any) {
        when (name) {
            "get" -> this.onGet(sessionId, futureId, *argv)
            "post" -> this.onPost(sessionId, futureId, *argv)
            else -> super.onReceive(name, sessionId, futureId, *argv)
        }
    }

    private fun onGet(sessionId: Long, futureId: Long, vararg argv: Any) {
        val url = argv[0].toStringValue()
        val headers = if (argv.size > 1) argv[1].toStringValue() else ""

        val requestBuilder = Request.Builder().url(url)
        if (headers != "") {
            requestBuilder.addHeaders(headers)
        }
        val req = requestBuilder.build()

        startRequest(req, sessionId, futureId)
    }

    private fun onPost(sessionId: Long, futureId: Long, vararg argv: Any) {
        val url = argv[0].toStringValue()
        val headers = if (argv.size > 1) argv[1].toStringValue() else ""
        val reqBody = if (argv.size > 2) argv[2].toStringValue() else ""
        val requestBody = reqBody.toRequestBody("application/json".toMediaTypeOrNull())

        val reqBuilder = Request.Builder()
            .post(requestBody)
            .url(url)
        if (headers != "") {
            reqBuilder.addHeaders(headers)
        }
        val req = reqBuilder.build()
        startRequest(req, sessionId, futureId)
    }

    private fun startRequest(request: Request, sessionId: Long, futureId: Long) {
        // 请求网络，获得结果后返回给服务器端
        fun send(code: Any, headers: Any, body: Any) =
            sendAction("onResult", sessionId, futureId, code, headers, body)

        csMain.launch {
            client?.newCall(request)?.enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    send(ERR_CODE.toAny(), "".toAny(), byteArrayOf(0).toAny())
                }

                override fun onResponse(call: Call, response: Response) {
                    val buffer = readBuf(response)
                    val headers = getHeaders(response)
                    send(response.code.toAny(), headers.toAny(), buffer.toAny())
                }
            })
        }
    }

    private fun readBuf(res: Response): ByteArray{
        // 读取 Response 的字节流到缓冲区
        val bos = ByteArrayOutputStream()
        val bis = res.body.byteStream()
        var n: Int
        bis.use { input ->
            bos.use { output ->
                while (input.read().also { n = it } != -1) {
                    output.write(n)
                }
            }
        }

        val buf = Buffer()
        val gos = GZIPOutputStream(buf.outputStream())
        gos.use {
            it.write(bos.toByteArray())
            it.flush()
        }
        return buf.readByteArray()
    }

    private fun Request.Builder.addHeaders(headers: String) {
        // 把一段Json文本添加到Request的头部信息
        val jsonObject = JSONObject(headers)
        val keys = jsonObject.keys()
        while (keys.hasNext()) {
            val key = keys.next() as String
            val value = jsonObject[key]
            header(key, value.toString())
        }
    }

    private fun getHeaders(response: Response) = JSONObject().let {
        // 获取Response响应头，转换成Json格式的字符串
        val headers = response.headers
        for (i in 0 until headers.size) {
            val name = headers.name(i)
            val value = headers.value(i)
            it.put(name, value)
        }
        it.toString()
    }
}
